// RealTimeSplitter.cpp
//
/////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2004 by Yves Tkaczyk
// (http://www.tkaczyk.net - yves@tkaczyk.net)
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License. 
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// Documentation: http://www.codeproject.com/property/treepropsheetex.asp
// CVS tree:      http://sourceforge.net/projects/treepropsheetex
//
//  /********************************************************************
//  *
//  * This code is an update of SimpleSplitter written by Robert A. T. Kaldy and 
//  * published on code project at http://www.codeproject.com/splitter/kaldysimplesplitter.asp.
//  *
//  *********************************************************************/
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "imcc.h"
#include "TreePropSheetSplitter.h"


#define FULL_SIZE 32768

inline int MulDivRound(int x, int mul, int div)
{
	return (x * mul + div / 2) / div;
}

BEGIN_MESSAGE_MAP(CTreePropSheetSplitter, CDialogBase)
	//{{AFX_MSG_MAP(CTreePropSheetSplitter)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_SIZE()
	ON_WM_NCCREATE()
	ON_WM_WINDOWPOSCHANGING()
	ON_WM_CREATE()
	ON_WM_SETCURSOR()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


CTreePropSheetSplitter::CTreePropSheetSplitter(const int nPanes, const UINT nOrientation, const int nMinSize, const int nBarThickness):
m_nPanes(nPanes),
m_nOrientation(nOrientation),
m_nBarThickness(nBarThickness),
m_nFrozenPaneCount(0),
m_bRealtimeUpdate(false),
m_bAllowUserResizing(true)
{
	ASSERT(nMinSize >= 0);

	// Common initialization
	CommonInit();
	// Set minimum pane sizes
	for ( int iPaneCnt = 0; iPaneCnt < m_nPanes; iPaneCnt++)
	{
		// Initialize minimum pane sizes.
		m_pzMinSize[iPaneCnt] = nMinSize;
	}
}

CTreePropSheetSplitter::CTreePropSheetSplitter(const int nPanes, const UINT nOrientation, const int* pzMinSize, const int nBarThickness):
m_nPanes(nPanes),
m_nOrientation(nOrientation),
m_nBarThickness(nBarThickness),
m_nFrozenPaneCount(0),
m_bRealtimeUpdate(false),
m_bAllowUserResizing(true)
{
	// Common initialization
	CommonInit();
	// Set minimum pane sizes
	for ( int iPaneCnt = 0; iPaneCnt < m_nPanes; iPaneCnt++)
	{
		// Initialize minimum pane sizes.
		m_pzMinSize[iPaneCnt] = pzMinSize[iPaneCnt];
	}

}

CTreePropSheetSplitter::~CTreePropSheetSplitter()
{
	delete[] m_pane;
	delete[] m_size;
	delete[] m_orig;
	delete[] m_frozen;
	delete[] m_pzMinSize;
}

///////////////////////////////////////////////////////////////////////////////
//
BOOL CTreePropSheetSplitter::Create(CWnd* pParent, const CRect& rect, UINT nID)
{
	ASSERT(pParent);
	BOOL bRet = CDialog::Create(CTreePropSheetSplitter::IDD, pParent);
	MoveWindow(rect);
	ShowWindow(SW_SHOW);

	return bRet;
}

///////////////////////////////////////////////////////////////////////////////
//
BOOL CTreePropSheetSplitter::Create(CWnd* pParent, UINT nID)
{
	CRect rcOuter;

	ASSERT(pParent);
	pParent->GetClientRect(&rcOuter);

	return Create( pParent, rcOuter, nID );
}

///////////////////////////////////////////////////////////////////////////////
//
BOOL CTreePropSheetSplitter::CreatePane(int nIndex, CWnd* pPaneWnd, DWORD dwStyle, DWORD dwExStyle, LPCTSTR lpszClassName)
{
	CRect rcPane;

	ASSERT((nIndex >= 0) && (nIndex < m_nPanes));
	m_pane[nIndex] = pPaneWnd;
	dwStyle |= WS_CHILD | WS_VISIBLE;
	GetPaneRect(nIndex, rcPane);
	return pPaneWnd->CreateEx(dwExStyle, lpszClassName, NULL, dwStyle, rcPane, this, AFX_IDW_PANE_FIRST + nIndex);
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetPane(int nIndex, CWnd* pPaneWnd)
{
	CRect rcPane;

	ASSERT((nIndex >= 0) && (nIndex < m_nPanes));
	ASSERT(pPaneWnd);
	ASSERT(pPaneWnd->m_hWnd);

	m_pane[nIndex] = pPaneWnd;
	GetPaneRect(nIndex, rcPane);
	pPaneWnd->MoveWindow(rcPane, false);
}

///////////////////////////////////////////////////////////////////////////////
//
CWnd* CTreePropSheetSplitter::GetPane(int nIndex) const
{
	ASSERT((nIndex >= 0) && (nIndex < m_nPanes));
	return m_pane[nIndex];
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetActivePane(int nIndex)
{
	ASSERT((nIndex >= 0) && (nIndex < m_nPanes));
	m_pane[nIndex]->SetFocus();
}

///////////////////////////////////////////////////////////////////////////////
//
CWnd* CTreePropSheetSplitter::GetActivePane(int& nIndex) const
{
	for ( int i = 0; i < m_nPanes; i++)
		if ( m_pane[i]->GetFocus())
		{
			nIndex = i;
			return m_pane[i];
		}
		return NULL;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetPaneSizes(const int* sizes)
{
	int i, total = 0, total_in = 0;

	for ( i = 0; i < m_nPanes; i++)
	{
		ASSERT(sizes[i] >= 0);
		total += sizes[i];
	}
	for ( i = 0; i < m_nPanes - 1; i++)
	{
		m_size[i] = MulDivRound(sizes[i], FULL_SIZE, total);
		total_in += m_size[i];
	}
	m_size[m_nPanes - 1] = FULL_SIZE - total_in;
	RecalcLayout();
	ResizePanes();
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::GetPaneRect(int nIndex, CRect& rcPane) const
{
	ASSERT(nIndex >= 0 && nIndex < m_nPanes);
	GetAdjustedClientRect(&rcPane);
	if ( m_nOrientation == SSP_HORZ)
	{
		rcPane.left = m_orig[nIndex];
		rcPane.right = m_orig[nIndex + 1] - m_nBarThickness;
	}
	else
	{
		rcPane.top = m_orig[nIndex];
		rcPane.bottom = m_orig[nIndex + 1] - m_nBarThickness;
	}
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::GetBarRect(int nIndex, CRect& rcBar) const
{
	ASSERT(nIndex > 0 && nIndex < m_nPanes - 1);
	GetAdjustedClientRect(&rcBar);
	if ( m_nOrientation == SSP_HORZ)
	{
		rcBar.left = m_orig[nIndex];
		rcBar.right = m_orig[nIndex + 1] - m_nBarThickness;
	}
	else
	{
		rcBar.top = m_orig[nIndex];
		rcBar.bottom = m_orig[nIndex + 1] - m_nBarThickness;
	}
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetFrozenPanes(const bool* frozenPanes)
{
	m_nFrozenPaneCount = 0;
	for( int i = 0; i < m_nPanes; ++i )
	{
		m_frozen[i] = frozenPanes[i];

		if( frozenPanes[i] )
			++m_nFrozenPaneCount;
	}
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::ResetFrozenPanes()
{
	for( int i = 0; i < m_nPanes; ++i )
	{
		m_frozen[i] = false;
	}

	m_nFrozenPaneCount = 0;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetRealtimeUpdate(const bool bRealtime)
{
	m_bRealtimeUpdate = bRealtime; 
}

///////////////////////////////////////////////////////////////////////////////
//
bool CTreePropSheetSplitter::IsRealtimeUpdate() const
{
	return m_bRealtimeUpdate;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::SetAllowUserResizing(const bool bAllowUserResizing)
{
	m_bAllowUserResizing  = bAllowUserResizing;

	// If the window exists, reset the cursor.
	if( ::IsWindow( GetSafeHwnd() ) )
	{
		::SetClassLong( GetSafeHwnd(), GCL_HCURSOR, (LONG)GetCursorHandle() );
	}
}

///////////////////////////////////////////////////////////////////////////////
//
bool CTreePropSheetSplitter::IsAllowUserResizing() const
{
	return m_bAllowUserResizing; 
}

///////////////////////////////////////////////////////////////////////////////
// Overridables
///////////////////////////////////////////////////////////////////////////////

void CTreePropSheetSplitter::UpdateParentRect(LPCRECT lpRectUpdate)
{
	GetParent()->InvalidateRect( lpRectUpdate, TRUE );
}

///////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////

HCURSOR CTreePropSheetSplitter::GetCursorHandle() const
{
	HCURSOR hCursor;

	LPCTSTR lpszCursor = IDC_ARROW;

	if( m_bAllowUserResizing )
		lpszCursor = m_nOrientation == SSP_HORZ ? IDC_SIZEWE : IDC_SIZENS;

	hCursor = (HCURSOR)::LoadImage(0, lpszCursor, IMAGE_CURSOR, 
		LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED);

	return hCursor;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::CommonInit()
{
	ASSERT(m_nPanes > 0);
	ASSERT(m_nOrientation == SSP_HORZ || m_nOrientation == SSP_VERT);
	ASSERT(m_nBarThickness >= 0);

	int total = 0;

	m_pane = new CWnd*[m_nPanes];	/// handed del
	m_size = new int[m_nPanes];		/// handed del
	m_orig = new int[m_nPanes + 1];	/// handed del
	m_pzMinSize = new int[m_nPanes];/// handed del
	m_frozen = new bool[m_nPanes];	/// handed del
	::ZeroMemory(m_pane, m_nPanes * sizeof(CWnd*));
	for ( int i = 0; i < m_nPanes - 1; i++)
	{
		// Initialize sizes. Default, set equal size to all panes
		m_size[i] = (FULL_SIZE + m_nPanes / 2) / m_nPanes;
		total += m_size[i];
	}

	m_size[m_nPanes - 1] = FULL_SIZE - total;
	ResetFrozenPanes();
}

///////////////////////////////////////////////////////////////////////////////
// CTreePropSheetSplitter protected
///////////////////////////////////////////////////////////////////////////////

void CTreePropSheetSplitter::RecalcLayout()
{
	int i, size_sum, remain, remain_new = 0;
	bool bGrow = true;
	CRect rcOuter;

	GetAdjustedClientRect(&rcOuter);

	size_sum = m_nOrientation == SSP_HORZ ? rcOuter.Width() : rcOuter.Height();
	size_sum -= (m_nPanes - 1) * m_nBarThickness;

	// Frozen panes
	/* Get the previous size and adjust the size of the frozen pane. We need to  
	do this as the size array contains ratio to FULL_SIZE. */

	// If we have some frozen columns use this algorithm otherwise adjust for minimum sizes.
	// If all the panes are frozen, do not use frozen pane algorithm.
	bool bFrozenOnly = m_nFrozenPaneCount && m_nPanes - m_nFrozenPaneCount;
	if( bFrozenOnly )
	{
		int prev_size_sum = m_orig[m_nPanes] - m_orig[0] - m_nBarThickness * m_nPanes;
		int non_frozen_count = m_nPanes - m_nFrozenPaneCount;
		int nTotalPanes = 0;
		for( int i = 0; i < m_nPanes; ++i )
		{
			if( i + 1 == m_nPanes )
			{
				// Assigned all remaining value to the last pane without computation.
				// This is done in order to prevent propagation of computation errors. 
				m_size[i] = FULL_SIZE - nTotalPanes;
			}
			else
			{
				if( m_frozen[i] )
				{ // Frozen pane
					m_size[i] = MulDivRound( m_size[i], prev_size_sum, size_sum);
				}
				else
				{ // Non frozen pane
					m_size[i] = MulDivRound( m_size[i],  prev_size_sum, size_sum ) +
						MulDivRound( size_sum - prev_size_sum, FULL_SIZE, size_sum * non_frozen_count );
				}
			}
			// If a pane become to small, it will be adjusted with the proportional algorithm.
			bFrozenOnly &= ( MulDivRound(m_size[i], size_sum, FULL_SIZE) <= m_pzMinSize[i] );
			// Compute running sumof pane sizes.
			nTotalPanes += m_size[i];
		}
	}

	/* The previous section might set the flag bFrozenOnly in case a pane became too small.
	In this case, we also want to execute this algorithm in order to respect the minimum
	pane size constraint. */
	if( !bFrozenOnly )
	{
		while ( bGrow)                           // adjust sizes on the beginning
		{                                       // and while we have growed something
			bGrow = false;
			remain = remain_new = FULL_SIZE;
			for ( i = 0; i < m_nPanes; i++)        // grow small panes to minimal size
				if ( MulDivRound(m_size[i], size_sum, FULL_SIZE) <= m_pzMinSize[i])
				{
					remain -= m_size[i];
					if ( MulDivRound(m_size[i], size_sum, FULL_SIZE) < m_pzMinSize[i])
					{
						if ( m_pzMinSize[i] > size_sum)
							m_size[i] = FULL_SIZE;
						else
							m_size[i] = MulDivRound(m_pzMinSize[i], FULL_SIZE, size_sum);
						bGrow = true;
					}
					remain_new -= m_size[i];
				}
				if ( remain_new <= 0)                  // if there isn't place to all panes
				{                                     // set the minimal size to the leftmost/topmost
					remain = FULL_SIZE;                 // and set zero size to the remainimg
					for ( i = 0; i < m_nPanes; i++)
					{
						if ( size_sum == 0)
							m_size[i] = 0;
						else
							m_size[i] = MulDivRound(m_pzMinSize[i], FULL_SIZE, size_sum);
						if ( m_size[i] > remain)
							m_size[i] = remain;
						remain -= m_size[i];
					}
					break;
				}
				if ( remain_new != FULL_SIZE)          // adjust other pane sizes, if we have growed some
					for ( i = 0; i < m_nPanes; i++)
						if ( MulDivRound(m_size[i], size_sum, FULL_SIZE) != m_pzMinSize[i])
							m_size[i] = MulDivRound(m_size[i], remain_new, remain);
		}
	}

	// calculate positions (in pixels) from relative sizes
	m_orig[0] = ( m_nOrientation == SSP_HORZ ? rcOuter.left:rcOuter.top );
	for ( i = 0; i < m_nPanes - 1; i++)
		m_orig[i + 1] = m_orig[i] + MulDivRound(m_size[i], size_sum, FULL_SIZE) + m_nBarThickness;
	m_orig[m_nPanes] = m_orig[0] + size_sum + m_nBarThickness * m_nPanes;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::ResizePanes()
{
	int i;
	CRect rcOuter;

	GetAdjustedClientRect(&rcOuter);
	if ( m_nOrientation == SSP_HORZ)	
		for ( i = 0; i < m_nPanes; i++)
		{
			if ( m_pane[i])
				m_pane[i]->MoveWindow(m_orig[i], rcOuter.top, m_orig[i + 1] - m_orig[i] - m_nBarThickness, rcOuter.Height());
		}
	else
		for ( i = 0; i < m_nPanes; i++)
		{
			if ( m_pane[i])
				m_pane[i]->MoveWindow(rcOuter.left, m_orig[i], rcOuter.Width(), m_orig[i + 1] - m_orig[i] - m_nBarThickness);
		}
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::InvertTracker()
{
	CDC* pDC = GetDC();
	CBrush* pBrush = CDC::GetHalftoneBrush();
	HBRUSH hOldBrush;

	CRect rcWnd;
	GetAdjustedClientRect(&rcWnd);

	hOldBrush = (HBRUSH)SelectObject(pDC->m_hDC, pBrush->m_hObject);
	if ( m_nOrientation == SSP_HORZ)
		pDC->PatBlt(m_nTracker - m_nBarThickness - rcWnd.left, 0, m_nBarThickness, m_nTrackerLength, PATINVERT);
	else
		pDC->PatBlt(0, m_nTracker - m_nBarThickness - rcWnd.top, m_nTrackerLength, m_nBarThickness, PATINVERT);
	if ( hOldBrush != NULL)
		SelectObject(pDC->m_hDC, hOldBrush);
	ReleaseDC(pDC);
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::GetAdjustedClientRect(CRect* pRect) const
{
	GetWindowRect(pRect);
	GetParent()->ScreenToClient(pRect);
}

///////////////////////////////////////////////////////////////////////////////
// CTreePropSheetSplitter messages
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::OnSize(UINT nType, int cx, int cy) 
{
	CRect rc;
	GetWindowRect(rc);
	if ( (0 == rc.Height()) || (0 == rc.Width()) )
	{
		return;
	}

	RecalcLayout();
	ResizePanes();

	CWnd::OnSize(nType, cx, cy);
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::OnLButtonDown(UINT nFlags, CPoint point) 
{
	UNREFERENCED_PARAMETER(nFlags);

	// Must be in AllowUserResizing mode.
	if( !m_bAllowUserResizing )
		return;

	CRect rcClient;
	int mouse_pos = m_nOrientation == SSP_HORZ ? point.x : point.y;

	SetCapture();
	for ( m_nTrackIndex = 1; (m_nTrackIndex < m_nPanes && m_orig[m_nTrackIndex] < mouse_pos); m_nTrackIndex++);
	m_nTracker = m_orig[m_nTrackIndex];
	m_nTrackerMouseOffset = mouse_pos - m_nTracker;
	GetWindowRect(&rcClient);
	GetParent()->ScreenToClient(&rcClient);
	m_nTrackerLength = m_nOrientation == SSP_HORZ ? rcClient.Height() : rcClient.Width();
	InvertTracker();
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::OnLButtonUp(UINT nFlags, CPoint point) 
{
	UNREFERENCED_PARAMETER(nFlags);
	UNREFERENCED_PARAMETER(point);

	if ( GetCapture() != this) 
		return;

	CRect rcOuter;
	int size_sum;

	GetAdjustedClientRect(&rcOuter);
	size_sum = m_nOrientation == SSP_HORZ ? rcOuter.Width() : rcOuter.Height();
	size_sum -= (m_nPanes - 1) * m_nBarThickness;

	InvertTracker();
	ReleaseCapture();
	m_orig[m_nTrackIndex] = m_nTracker;
	m_size[m_nTrackIndex - 1] = MulDivRound(m_orig[m_nTrackIndex] - m_orig[m_nTrackIndex - 1] - m_nBarThickness, FULL_SIZE, size_sum);
	m_size[m_nTrackIndex]     = MulDivRound(m_orig[m_nTrackIndex + 1] - m_orig[m_nTrackIndex] - m_nBarThickness, FULL_SIZE, size_sum);
	ResizePanes();
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::OnMouseMove(UINT nFlags, CPoint point) 
{
	UNREFERENCED_PARAMETER(nFlags);

	if ( GetCapture() != this) 
		return;
	InvertTracker();

	m_nTracker = (m_nOrientation == SSP_HORZ ? point.x : point.y) - m_nTrackerMouseOffset;
	ASSERT( m_nTrackIndex > 0 );
	if ( m_nTracker > m_orig[m_nTrackIndex + 1] - m_nBarThickness - m_pzMinSize[m_nTrackIndex])
	{
		m_nTracker = m_orig[m_nTrackIndex + 1] - m_nBarThickness - m_pzMinSize[m_nTrackIndex];
	}
	else if ( m_nTracker < m_orig[m_nTrackIndex - 1] + m_nBarThickness + m_pzMinSize[m_nTrackIndex-1])
	{
		m_nTracker = m_orig[m_nTrackIndex - 1] + m_nBarThickness + m_pzMinSize[m_nTrackIndex-1];
	}

	InvertTracker();

	CRect rcOuter;
	GetAdjustedClientRect(&rcOuter);
	// Real-time update.
	if( m_bRealtimeUpdate )
	{
		m_orig[m_nTrackIndex] = m_nTracker;
		ResizePanes();
	}
}

///////////////////////////////////////////////////////////////////////////////
//
BOOL CTreePropSheetSplitter::OnNcCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if ( !CWnd::OnNcCreate(lpCreateStruct))
		return FALSE;

	CWnd* pParent = GetParent();
	ASSERT_VALID(pParent);
	pParent->ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_DRAWFRAME);
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//
void CTreePropSheetSplitter::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
	lpwndpos->flags |= SWP_NOCOPYBITS;
	CWnd::OnWindowPosChanging(lpwndpos);
}

BOOL CTreePropSheetSplitter::OnInitDialog()
{
	CDialogBase::OnInitDialog();

	SetDpi(IDD);

	dsSkinWindow(GetSafeHwnd(), SKIN_TYPE_CHILDDIALOG, "trans_dlg", TRUE);

	return TRUE;  // return TRUE unless you set the focus to a control
}

BOOL CTreePropSheetSplitter::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if ( pWnd->m_hWnd == m_hWnd)
	{
		SetCursor(GetCursorHandle());
	}
	else
	{
		SetCursor((HCURSOR)::LoadImage(0, IDC_ARROW, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED));
	}

	return TRUE;
}



